/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */

package com.liycloud.common.security.mobile;

// pigx
import com.liycloud.common.security.component.PigxPreAuthenticationChecks;
import com.liycloud.common.security.service.PigxUserDetailsService;
import com.liycloud.common.security.util.PigxSecurityMessageSourceUtil;

import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

// spring-security
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;

/**
 * @author lengleng
 * @date 2018/8/5 手机登录校验逻辑 验证码登录、社交登录
 *
 *  spring-security 由 ProviderManager 统一管理所有的 List< AuthenticationProvider >
 *
 *  AuthenticationProvider  接口 两个方法 supports (是否支持)  authenticate (核心认证方法)
 *
 *  AuthenticationProvider 的实现类才是真正执行认证的类
 *
 */
@Slf4j
public class MobileAuthenticationProvider implements AuthenticationProvider {


	private MessageSourceAccessor messages = PigxSecurityMessageSourceUtil.getAccessor();

	// 用户信息前置认证校验器,   检查用户状态信息:  是否锁定, 是否可用, 是否过期
	private UserDetailsChecker detailsChecker = new PigxPreAuthenticationChecks();

	@Getter
	@Setter
	private PigxUserDetailsService userDetailsService;


	/**
	 * 认证核心方法   ( 方法的核心内涵:  框架传递过来一个 Authentication 对象 )
	 */
	@Override
	@SneakyThrows
	public Authentication authenticate(Authentication authentication) {

		// 原始传入的认证对象 转换成 MobileAuthenticationToken,
		MobileAuthenticationToken originalMobileAuthenticationToken = (MobileAuthenticationToken) authentication;

		// 得到主要的内容 principal
		String principal = originalMobileAuthenticationToken.getPrincipal().toString();

		// 自定义用户信息服务类 PigxUserDetailsService  继承 UserDetailsService,  通过社交登录码code 加载获取 UserDetails
		UserDetails userDetails =  userDetailsService.loadUserBySocial(principal);

		if (userDetails == null) {  //未得到

			log.debug("Authentication failed: no credentials provided");    //  n. 证书；文凭；信任状（credential ）
			 																// v. 得到信用；授…以证书（credential ）
			throw new BadCredentialsException(
					 messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount",
					"Noop Bind Account"));
		}

		// 检查账号状态
		detailsChecker.check(userDetails);

		// 重新实例化认证Token对象
		MobileAuthenticationToken authenticationToken = new MobileAuthenticationToken(userDetails, userDetails.getAuthorities());

		authenticationToken.setDetails( originalMobileAuthenticationToken.getDetails() );

		return authenticationToken;
	}


	/**
	 * 是否支持的逻辑是:  传入的 authentication 对象 的类型是否是 MobileAuthenticationToken 这个类型
	 */
	@Override
	public boolean supports(Class<?> authentication) {
		return MobileAuthenticationToken.class.isAssignableFrom(authentication);
	}

}
